﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace PayloadTracker
{
    public delegate void OnZoomChangedDelegate(int Zoom);
    public delegate void OnMapClickDelegate(Point p, Coordinate c);
    public delegate void OnFlightSelectedDelegate(Flight f);
    public partial class MapPanel : UserControl
    {
        public event OnZoomChangedDelegate OnZoomChanged;
        public event OnMapClickDelegate OnMapClick;
        public event OnFlightSelectedDelegate OnFlightSelected;
        int m_Zoom;
        Coordinate m_Center;
        APRSSymbols m_APRSSymbols = new APRSSymbols();
        FlightCollection m_Flights;
        Dictionary<string, Image> m_LoadedImages;
        Flight m_Pin;

        Tiles.TileManager m_TileManager;

        bool m_IsDragging;
        Point m_PrevMousePos;



        #region Accessors

        public Coordinate Center
        {
            get { return m_Center;  }
        }
        public int Zoom
        {
            get { return m_Zoom; }
            set { 
                m_Zoom = value; this.Invalidate();
            
            OnZoomChangedDelegate handler = OnZoomChanged;
            if(handler != null)
            handler(value);
            }
        }
        public FlightCollection Flights
        {
            get { return m_Flights; }
            set
            {
                m_Flights = value;

                if (m_Flights != null)
                    foreach (Flight f in m_Flights)
                    {
                        var bub = f.GetBubble();
                        bub.PinRequest += new PinRequestDelegate(bub_PinRequest);
                        Controls.Add(bub);
                    }
            }
        }
        #endregion

        void bub_PinRequest(Flight f)
        {
            if (m_Pin != f)
                m_Pin = f;
            else
                m_Pin = null;

            this.Invalidate();
        }

        public MapPanel()
        {


            InitializeComponent();
            this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
            this.UpdateStyles();


            m_IsDragging = false;
            m_Flights = null;
            m_TileManager = new PayloadTracker.Tiles.TileManager();
            m_TileManager.TileInvalidated += new PayloadTracker.Tiles.TileInvalidatedDelegate(m_TileManager_TileInvalidated);
            m_LoadedImages = new Dictionary<string, Image>();
            m_Center = new Coordinate(47.561632,-122.339186);
            m_Zoom = 5;

            this.Resize += new EventHandler(MapPanel_Resize);

            this.MouseDown += new MouseEventHandler(MapPanel_MouseDown);
            this.MouseUp += new MouseEventHandler(MapPanel_MouseUp);
            this.MouseMove += new MouseEventHandler(MapPanel_MouseMove);

            this.MouseDoubleClick += new MouseEventHandler(MapPanel_MouseDoubleClick);

        }

        void MapPanel_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            SetCenter(PixelPositionToCoordinate(e.Location));
            m_PrevMousePos = e.Location;
            if(Zoom < 16)
                Zoom++;
        }

        void MapPanel_MouseMove(object sender, MouseEventArgs e)
        {

            if (m_IsDragging)
            {
                m_Pin = null;

                Point mousedelta = new Point(e.X - m_PrevMousePos.X, e.Y - m_PrevMousePos.Y);
                SetCenter(PixelPositionToCoordinate(new Point(Width / 2 - mousedelta.X, Height / 2 - mousedelta.Y)));
                m_PrevMousePos = e.Location;
            }
        }

        void MapPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (m_IsDragging && e.Button == MouseButtons.Left)
                m_IsDragging = false;
        }

        void MapPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                m_PrevMousePos = e.Location;
                m_IsDragging = true;
            }

        }

        void MapPanel_Resize(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        void m_TileManager_TileInvalidated(PayloadTracker.Tiles.Tile t)
        {
            this.Invalidate();
        }
        private void MapPanel_Load(object sender, EventArgs e)
        {

        }

        public void SetCenter(Coordinate c)
        {
            m_Center = c;
            this.Invalidate();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            if (m_Pin != null && m_Pin.CurrentTelemetry != null)
                m_Center = m_Pin.CurrentTelemetry.Position;

            //Background fill
            e.Graphics.FillRectangle(Brushes.LightSkyBlue, new Rectangle(0, 0, this.Width, this.Height));

            if (m_Center == null)
                return;


            var screenbb = GetScreenBoundingBox();


            bool first = true;
            foreach (var tl in m_TileManager.GetTilesForRegion(screenbb.UL, screenbb.LR, m_Zoom,true))
            {
                
                foreach (var tile in tl)
                {
                    var seg_ul = CoordinateToPixelPosition(tile.UL);
                    float[][] matrix ={
                                            new float[] {1, 0, 0, 0, 0},
                                            new float[] {0, 1, 0, 0, 0},
                                            new float[] {0, 0, 1, 0, 0},
                                            new float[] {0, 0, 0, first?1f:0.5f, 0},
                                            new float[] {0, 0, 0, 0, 1}
                                        };



                    ColorMatrix cm = new ColorMatrix(matrix);
                    ImageAttributes ia = new ImageAttributes();
                    ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

                    e.Graphics.DrawImage(tile.Image, new Rectangle(seg_ul, tile.Image.Size),
                        0,0,tile.Image.Width,tile.Image.Height, GraphicsUnit.Pixel, ia);

                    //e.Graphics.DrawImageUnscaled(tile.Image, seg_ul);
                }

                if (first)
                    first = false;
            }

            foreach (VOR.VOR v in VOR.VORManager.Instance.VORs)
            {
                var pt = CoordinateToPixelPosition(v.Position);
                e.Graphics.FillEllipse(System.Drawing.Brushes.Red, pt.X - 4, pt.Y - 4, 8, 8);
                e.Graphics.DrawString(v.Name, SystemFonts.DefaultFont, Brushes.Red, pt, StringFormat.GenericDefault);
            }


            if (m_Flights == null)
                return;


 

            foreach (Flight f in Flights)
            {
                if (f.VisibleTrack)
                {
                    List<Point> breadcrumbs = new List<Point>();
                    //draw the mapticks
                    lock (f.Path)
                        foreach (var beacon in f.Path)
                        {
                            var loc = CoordinateToPixelPosition(beacon.Position);
                            breadcrumbs.Add(loc);
                        }

                    if(breadcrumbs.Count > 1)
                        e.Graphics.DrawLines(new Pen(f.LineColor, 5), breadcrumbs.ToArray());
                }
                if (f.VisibleBlip && f.CurrentTelemetry != null)
                {
                    //draw the blip
                    var centerpoint = CoordinateToPixelPosition(f.CurrentTelemetry.Position);
                    centerpoint.X -= 7;
                    centerpoint.Y -= 7;
                    Bitmap bmp = m_APRSSymbols.tile((string)f.ImageIndex[0], (int)f.ImageIndex[1]);
                    bmp.MakeTransparent(Color.White);
                    e.Graphics.DrawImage(bmp, centerpoint);
                    
                }

                if (f.VisibleInfoBubble && f.CurrentTelemetry != null)
                {
                    //draw triangle from flight position to the start of the ellipse.
                    var fpos = CoordinateToPixelPosition(f.CurrentTelemetry.Position);

                    f.GetBubble().Location = new Point(fpos.X, fpos.Y - f.GetBubble().Height);

                }
            }

            base.OnPaint(e);

        }

        public Point CoordinateToPixelPosition(Coordinate c)
        {
            Point tilepoint = Tiles.OSMProvider.Coordinate2Tile(c, m_Zoom);
            Point centpoint = Tiles.OSMProvider.Coordinate2Tile(m_Center, m_Zoom);

            //Get upper left corner of the center tile.
            var centbb = Tiles.OSMProvider.Tile2BoundingBox(centpoint, m_Zoom);
            var centrelpos = GetRelPosofCoordinateInBBLinear(m_Center, centbb);
            Point centULpoint = new Point
                (
                (this.Width / 2) - ((int)(0.5f + (centrelpos.Long * Tiles.Tile.SIZE))),
                (this.Height / 2) - ((int)(0.5f + (centrelpos.Lat * Tiles.Tile.SIZE)))
                );



            //Get upperleft coords of the maptile.
            BoundingBox bb = Tiles.OSMProvider.Tile2BoundingBox(tilepoint, m_Zoom);

            var relpos = GetRelPosofCoordinateInBBLinear(c, bb);

            int tilediffX = centpoint.X - tilepoint.X;
            int tilediffY = centpoint.Y - tilepoint.Y;

            int targettilescreenL = centULpoint.X - (Tiles.Tile.SIZE * tilediffX);
            int targettilescreenU = centULpoint.Y - (Tiles.Tile.SIZE * tilediffY);

            Point ret = new Point();

            ret.X = targettilescreenL + ((int)(relpos.Long * Tiles.Tile.SIZE));
            ret.Y = targettilescreenU + ((int)(relpos.Lat * Tiles.Tile.SIZE));

            return ret;


        }
        public Coordinate PixelPositionToCoordinate(Point p)
        {

            var sbb = GetScreenBoundingBox();


            double yrel = ((double)p.Y) / ((double)this.Height);
            double xrel = ((double)p.X) / ((double)this.Width);

            double lat = (sbb.North - sbb.GetLatSpan() * yrel);
            double lon = (sbb.West + sbb.GetLongSpan() * xrel);

            return new Coordinate(lat, lon);

        }
        public BoundingBox GetScreenBoundingBox()
        {
            var ctbb = Tiles.OSMProvider.Tile2BoundingBox(Tiles.OSMProvider.Coordinate2Tile(m_Center, m_Zoom), m_Zoom);

            double latspan_2 = (1.0f * ctbb.GetLatSpan() * this.Height / Tiles.Tile.SIZE) / 2;
            double longspan_2 = (1.0f * ctbb.GetLongSpan() * this.Width / Tiles.Tile.SIZE) / 2;

            return new BoundingBox
                (
                    m_Center.Lat + latspan_2,
                    m_Center.Long + longspan_2,
                    m_Center.Lat - latspan_2,
                    m_Center.Long - longspan_2
                );


        }
        private Coordinate GetRelPosofCoordinateInBBLinear(Coordinate c, BoundingBox bb)
        {
            Coordinate ret = new Coordinate();
            ret.Lat = ((float)((bb.North - c.Lat) / bb.GetLatSpan()));
            ret.Long = 1.0f - ((float)((bb.East - c.Long) / bb.GetLongSpan()));

            return ret;
        }

        private void MapPanel_MouseClick(object sender, MouseEventArgs e)
        {
            OnMapClickDelegate handler = OnMapClick;
            if (handler != null)
                handler(e.Location, PixelPositionToCoordinate(e.Location));

            //see if we clicked a flight.
            if (Flights != null)
                foreach (Flight f in Flights)
                {
                    if (f.CurrentTelemetry != null)
                    {
                        var flightpos = CoordinateToPixelPosition(f.CurrentTelemetry.Position);
                        if (Math.Sqrt(Math.Pow(flightpos.X - e.X, 2) + Math.Pow(flightpos.Y - e.Y, 2)) < 7)
                        {//we clicked this flight.

                            OnFlightSelectedDelegate ofshandler = OnFlightSelected;
                            if (ofshandler != null)
                                ofshandler(f);

                            f.VisibleInfoBubble = !f.VisibleInfoBubble;
                            this.Invalidate();

                            break;
                        }
                    }
                }
        }

        private void MapPanel_KeyPress(object sender, KeyPressEventArgs e)
        {
            int scrollrate = 12;

            if (e.KeyChar == 'h')
            {
                SetCenter(new Coordinate(m_Center.Lat, PixelPositionToCoordinate(new Point(Width / 2 - scrollrate, Height / 2)).Long));
            }
            else if (e.KeyChar == 'j')
            {
                SetCenter(new Coordinate(PixelPositionToCoordinate(new Point(Width / 2, Height / 2 - scrollrate)).Lat, m_Center.Long));
            }
            else if (e.KeyChar == 'k') 
            {
                SetCenter(new Coordinate(PixelPositionToCoordinate(new Point(Width / 2, Height / 2 + scrollrate)).Lat, m_Center.Long));
            }
            else if (e.KeyChar == 'l')
            {
                SetCenter(new Coordinate(m_Center.Lat, PixelPositionToCoordinate(new Point(Width / 2 + scrollrate, Height / 2)).Long));
            }
            else if(e.KeyChar >= '1' && e.KeyChar <= '9')
            {
                int flight = e.KeyChar - '1';

                if (m_Flights.Flights.Count >= flight + 1)
                bub_PinRequest(m_Flights.Flights[flight]);

            }
        }
    }
}
